package com.gopay.checkorder.service.sentinel.impl;

import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

import com.gopay.checkorder.dao.BankReturnMsgForFusedDAO;
import com.gopay.checkorder.dao.BankSentinelInfoDAO;
import com.gopay.checkorder.service.sentinel.BcdlOrderSentinel;
import com.gopay.common.constants.bank.BankConstants;
import com.gopay.common.constants.proccode.ProcCodeConstants;
import com.gopay.common.domain.bank.BankOrder;
import com.gopay.common.domain.bank.BcdlResult;
import com.gopay.common.domain.bank.bcdl.BcdlPacketRes;
import com.gopay.common.domain.bank.bcdl.BisBankReturnMsgForFused;
import com.gopay.common.domain.bank.bcdl.BisOneFenTestBankAcct;
import com.gopay.common.domain.cps.vo.CheckOrderResult;
import com.gopay.common.order.dao.CommonQueryDAO;
import com.gopay.common.user.dao.BisOneFenTestBankAcctDAO;
import com.gopay.common.user.dao.BisOneFenTestOrderDAO;
import com.gopay.common.util.DateUtils;
import com.gopay.remote.bank.onefentest.OneFenTestRemoteService;
import com.gopay.remote.bank.onefentest.vo.OneFenTestVo;
import com.gopay.sms.client.GopaySMSHttpClient;
import com.gopay.sms.common.constants.SMSAppId;
import com.gopay.sms.common.domain.RequestVO;
import com.gopay.sms.common.domain.ResponseVO;


/**
 * 银行熔断机制
 *
 * @author lilili
 * @data
 */public class IcbcBcdlOrderSentinelImpl implements BcdlOrderSentinel{

	private final static Logger logger = LoggerFactory.getLogger(IcbcBcdlOrderSentinelImpl.class);
	@Resource(name = "commonQueryDAO")
	private CommonQueryDAO commonQueryDAO;
    @Resource(name = "bisOneFenTestBankAcctDAO")
    private BisOneFenTestBankAcctDAO bisOneFenTestBankAcctDAO;
	@Resource(name = "bankReturnMsgForFusedDAO")
	private BankReturnMsgForFusedDAO bankReturnMsgForFusedDAO;

	@Resource(name = "bankSentinelInfoDAO")
	private BankSentinelInfoDAO bankSentinelInfoDAO;
	@Resource(name = "oneFenTestClient")
	OneFenTestRemoteService oneFenTestClient;

	private static final String SWITCH_ON = "on";

	@Resource(name = "smsConfig")
	private Map<String, String> smsConfig;

	@Resource(name = "bcdlOrderSentinelFacade")
	private BcdlOrderSentinelFacade bcdlOrderSentinelFacade;
	
	/**
	 * 熔断机制true-中断 false-通路
	 */
	private Boolean fuseWire = false;

	private AtomicInteger timeoutCount = new AtomicInteger(0);

	private  ConcurrentHashMap<String, String> fusedSet = new  ConcurrentHashMap<String, String>(10);
	
	private  ConcurrentHashMap<String, String> noCodeSet = new ConcurrentHashMap<String, String>(10);
	
	private AtomicInteger sysErrorCount = new AtomicInteger(0);;
	
	private AtomicInteger batchTimeoutCount = new AtomicInteger(0);

	private String bank;

	private String stlmEx;

	private Integer maxTime;

	private String sentinelId;
	
	@Resource(name = "bcdlConfig")
	private Map<String, String> bcdlConfig;

	/**
	 * 项目启动时初始化fuseWire
	 */
	public void initFuseWire(){
		fuseWire = bankSentinelInfoDAO.findByStlmEx(sentinelId);
	}

	@Override
	public Boolean isFuse() {
		logger.error("[sentinel_patrol_isFuse] bank=" + bank + ", maxTime=" + maxTime);
		return fuseWire;
	}

	@Override
	public Boolean restart() {
		timeoutCount.set(0);;
		sysErrorCount.set(0);
		fusedSet.clear();
		noCodeSet.clear();
		batchTimeoutCount.set(0);
		fuseWire = false;

		bankSentinelInfoDAO.updateSentinel(0, 0, sentinelId, "", "");
		return true;
	}

	@Override
	public Boolean stop(int flag) {//0手工熔断 1熔断器逻辑熔断 2任务未完成熔断 3插入防重日志表异常
		fuseWire = true;
		bankSentinelInfoDAO.updateSentinel(flag, 1, sentinelId, "", "");
		return true;
	}

	@Override
	public void patrol(BankOrder bankOrder, BcdlResult result) {
		if (isFuse()) {
			logger.error("[sentinel_patrol_error]--付款任务已熔断，不再处理"+bankOrder.getOrderId());
			return;
		}
		logger.error("[sentinel_patrol_start]" + bank + "," + bankOrder.getOrderId());
		try {
			if (null == result) {
				fuseWiring("未知结果中断付款作业", bankOrder.getOrderId());
				return;
			}
			if (ProcCodeConstants.PROC_CODE_100E6082.equals(result.getResultCode())) {
				// 银企直连类交易转账签名异常,中断交易发起
				fuseWiring("签名异常中断付款作业", bankOrder.getOrderId());
				return;
			}
			if (ProcCodeConstants.PROC_CODE_100E6027.equals(result.getResultCode()) ||  ProcCodeConstants.PROC_CODE_100E6066.equals(result.getResultCode())) {
				//调用熔断处理逻辑
				fused(bankOrder,result.getBankRetCode(),result.getErrorMsg());
			}
			if (ProcCodeConstants.PROC_CODE_100E6047.equals(result.getResultCode())) {
				timeoutCount.incrementAndGet();
				if (timeoutCount.get()==5) {
					fuseWiring("连续5笔超时中断付款作业", bankOrder.getOrderId());
					return;
				}
			} else {
				timeoutCount.set(0);;
			}

		} catch (Exception e) {
			logger.error("[sentinel_patrol_error]" + bank + ",哨兵异常" + "," + bankOrder.getOrderId(), e);
			fuseWiring("哨兵异常中断付款作业", bankOrder.getOrderId());
			return;
		}
		logger.error("[sentinel_patrol_end]" + bank + "," + bankOrder.getOrderId());
	}

	@Override
	public void checkPartol(BankOrder bankOrder, CheckOrderResult result) {
		if (isFuse()) {
			logger.error("[sentinel_patrol_error]--付款任务已熔断，不再处理"+bankOrder.getOrderId());
			return;
		}
		logger.error("[sentinel_patrol_start]" + bank + "," + bankOrder.getOrderId());
		try {
			if (null == result) {
				fuseWiring("未知结果中断付款作业", bankOrder.getOrderId());
				return;
			}

			//调用熔断处理逻辑
			if (ProcCodeConstants.PROC_CODE_100E6027.equals(result.getErrCode()) ||  ProcCodeConstants.PROC_CODE_100E6066.equals(result.getErrCode())) {
				fused(bankOrder,result.getBankRetCode(),result.getErrMsg());
			}
			if (ProcCodeConstants.PROC_CODE_100E6047.equals(result.getErrCode())) {
				timeoutCount.incrementAndGet();
				if (timeoutCount.get()==5) {
					fuseWiring("连续5笔超时中断付款作业", bankOrder.getOrderId());
					return;
				}
			} else {
				timeoutCount.set(0);;
			}
//			if (ProcCodeConstants.PROC_CODE_100F1002.equals(result.getErrCode())) {
//				sysErrorCount.incrementAndGet();
//				if (sysErrorCount.get() == 5) {
//					fuseWiring("连续5笔内部系统异常中断付款作业", bankOrder.getOrderId());
//					return;
//				}
//			} else {
//				sysErrorCount.set(0);;
//			}

		} catch (Exception e) {
			logger.error("[sentinel_patrol_error]" + bank + ",哨兵异常" + "," + bankOrder.getOrderId(), e);
			fuseWiring("哨兵异常中断付款作业", bankOrder.getOrderId());
			return;
		}
		logger.error("[sentinel_patrol_end]" + bank + "," + bankOrder.getOrderId());
	}
	
	
	public void insertSentinelReturnCode(String bankRetCode,String errMsg) {
		logger.error("插入熔断错误码start");
		BisBankReturnMsgForFused returnMsgForFused = new BisBankReturnMsgForFused();
		returnMsgForFused.setStlmEx(stlmEx);
		returnMsgForFused.setIsFused(2);
		returnMsgForFused.setBankCode(bank);
		returnMsgForFused.setReturnCode(bankRetCode);
		returnMsgForFused.setCreateTs(new Date());
		bankReturnMsgForFusedDAO.save(returnMsgForFused);
		logger.error("插入熔断错误码end");
	}
	
	public synchronized void fuseWiring(String msg, String gopayOrderId) {
		fuseWire = true;

		bankSentinelInfoDAO.updateSentinel(1, 1, sentinelId, msg, gopayOrderId);

		logger.error("[fuseWiring]" + bank + "," + msg + "," + gopayOrderId);
		sendSms(msg, gopayOrderId);
		/*
		//如果是周末，就使用自动解熔断
		if(isWeekEnd(new Date())){
			先调用一分钱测试，如果通过了，就自动解除熔断
			//一分钱测试
			BisOneFenTestBankAcct acct = getBisOneFenTestBankAcctBysentinelCode(sentinelId);
			
			OneFenTestVo vo = new OneFenTestVo();
			vo.setSentinelCode(acct.getSentinelCode());
			vo.setBankProvinceName(acct.getBankProvinceName());
			vo.setBankCityName(acct.getBankCityName());
			vo.setBankBranchName(acct.getBankBranchName());
			vo.setBankAcctNum(acct.getBankAcctNum());
			vo.setBankAcctName(acct.getBankAcctName());
			vo.setBankName(acct.getBankName());
			String onTestOrderId = oneFenTestClient.genOneFenTestOrder(vo);
			logger.error("熔断器" + sentinelId + "一分钱测试通过,测试的orderId:" + onTestOrderId + "，准备自动解除熔断");
			if(StringUtils.isNotEmpty(onTestOrderId)//如果通过就解除熔断
					&& bankSentinelInfoDAO.findByStlmEx(sentinelId)){//熔断器处于熔断状态
				 //解除熔断
				 if(restart()){
					 logger.error("熔断器" + sentinelId + "自动解除熔断完成：成功");
					//发送短信
					 sendAutoRestartSms(gopayOrderId, onTestOrderId);
				 } else {
					 logger.error("熔断器" + sentinelId + "自动解除熔断完成：失败");
				 }
			}
		}*/
	}
	
	/**
	 * 
	 * @Description: 根据熔断器编号获取一分钱测试银行卡账户 
	 * @param sentinelCode
	 * @return 
	 * @author admin
	 */
    private BisOneFenTestBankAcct getBisOneFenTestBankAcctBysentinelCode(String sentinelCode) {
        StringBuilder hql = new StringBuilder("select b from BisOneFenTestBankAcct b where b.sentinelCode=:sentinelCode");
        Map<String,Object> map = new HashMap<>();
        map.put("sentinelCode",Long.parseLong(sentinelCode));
        List<BisOneFenTestBankAcct> list = bisOneFenTestBankAcctDAO.findByHql(hql.toString(), map);
        if(CollectionUtils.isEmpty(list)) {
            return null;
        }else{
            return list.get(0);
        }
    }

	public static void main(String[] args) {
		System.out.println(getWeekOfDate(new Date()));
		
	}
	
	private boolean isWeekEnd(Date dt){
		 Calendar cal = Calendar.getInstance();
	     cal.setTime(dt);
	     int w = cal.get(Calendar.DAY_OF_WEEK) - 1;
	     if (w < 0){w = 0;}
	     if(w == 0 || w == 6){
	    	 return true;
	     }
	     return false;
	}
	
	 public static String getWeekOfDate(Date dt) {
	        String[] weekDays = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
	        Calendar cal = Calendar.getInstance();
	        cal.setTime(dt);

	        int w = cal.get(Calendar.DAY_OF_WEEK) - 1;
	        if (w < 0)
	            w = 0;

	        return weekDays[w];
	    }
	
	private void sendSms(String msg, String gopayOrderId) {

		try {
			if (SWITCH_ON.equals(smsConfig.get("switch"))) {
				// 自定义短信内容，不提供验证码校验功能
				RequestVO req = new RequestVO();
				req.setAppId(SMSAppId._90000.value);
				req.setMobile(smsConfig.get("mobile"));// 电话号码(多个以逗号分隔)
				if (gopayOrderId.startsWith("PacketNo")) {
					req.setContent(DateUtils.getCurrDate() + "," + bank + " 熔断:" + msg + ", " + gopayOrderId);
				} else {
					req.setContent(DateUtils.getCurrDate() + "," + bank + " 熔断:" + msg + ", gopayOrderId:" + gopayOrderId);
				}
				ResponseVO resp = GopaySMSHttpClient.getInstance().createVerificationCode(req);

				if (resp != null) {
					if ("100".equals(resp.getStatusCode())) {
						logger.error("[sentinel_sms]银行【" + bank + "】熔断短信发送成功");
					} else {
						logger.error("[sentinel_sms]银行【" + bank + "】熔断短信发送失败，原因：" + resp.getMessage());
					}
				}
			} else {
				logger.error("[sentinel_sms] 熔断短信发送已关闭，信息: 银行" + bank);

			}
		} catch (Exception e) {
			logger.error("[sentinel_sms]senSMS error: ", e);
		}

	}
	
	/**
	 * @Description: 自动解除熔断发送短信
	 * @param msg
	 * @param gopayOrderId
	 * @param onTestOrderId 
	 * @author admin
	 */
	private void sendAutoRestartSms(String gopayOrderId, String onTestOrderId) {

		try {
			if (SWITCH_ON.equals(smsConfig.get("switch"))) {
				// 自定义短信内容，不提供验证码校验功能
				RequestVO req = new RequestVO();
				req.setAppId(SMSAppId._90000.value);
				req.setMobile(smsConfig.get("mobile"));// 电话号码(多个以逗号分隔)
				if (gopayOrderId.startsWith("PacketNo")) {
					req.setContent(DateUtils.getCurrDate() + "," + bank + "，一分钱测试通过(测试orderId为 " + onTestOrderId + ")，自动解除熔断成功, " + gopayOrderId);
				} else {
					req.setContent(DateUtils.getCurrDate() + "," + bank + "，一分钱测试通过(测试orderId为 " + onTestOrderId + ")，自动解除熔断成功, gopayOrderId:" + gopayOrderId);
				}
				ResponseVO resp = GopaySMSHttpClient.getInstance().createVerificationCode(req);

				if (resp != null) {
					if ("100".equals(resp.getStatusCode())) {
						logger.error("[sentinel_sms]银行【" + bank + "】自动解除熔断短信发送成功");
					} else {
						logger.error("[sentinel_sms]银行【" + bank + "】自动解除熔断短信发送失败，原因：" + resp.getMessage());
					}
				}
			} else {
				logger.error("[sentinel_sms] 熔断短信发送已关闭，信息: 银行" + bank);

			}
		} catch (Exception e) {
			logger.error("[sentinel_sms]senSMS error: ", e);
		}

	}
	
	
	/**
	 * 熔断处理
	 * @param bankOrder
	 * @param bankRetCode
	 * @param errMsg
	 */
	private void fused(BankOrder bankOrder, String bankRetCode, String errMsg) {

		if (StringUtils.isNotBlank(bankRetCode)) {
			List<BisBankReturnMsgForFused> list =bankReturnMsgForFusedDAO.getBankReturnMsgByCode(bankRetCode, stlmEx);
			if (list.size()>0) {
				BisBankReturnMsgForFused bisbankReturnMsg = list.get(0);// 获取到当前错误码信息
				
				if (bisbankReturnMsg.getIsFused() == 1) {
					// 银企直连类交易指令处理失败
					fuseWiring("银行返回" + errMsg + "中断付款作业", bankOrder.getOrderId());
					return;
				} else if (bisbankReturnMsg.getIsFused() == 0) {
					logger.error("银行返回" + errMsg + "不做熔断处理," + bankOrder.getOrderId());
				} else {
					logger.error("银行返回" + errMsg + "," + bankOrder.getOrderId());
					fusedSet.put(bankOrder.getOrderId(),bankOrder.getOrderId());
					if (fusedSet.size() == maxTime) {
						fuseWiring("累计10笔银行返回错误信息未知熔断状态中断付款作业：" + errMsg + ",", bankOrder.getOrderId());
						return;
					}

				}
			} else {
				logger.error("银行返回" + errMsg + "," + bankOrder.getOrderId());
				// 将错误码插入数据库并刷新缓存
				fuseWiring("银行返回不存在错误信息中断付款作业," + errMsg, bankOrder.getOrderId());
				insertSentinelReturnCode(bankRetCode, errMsg);
				return;
			}
		} else {
			logger.error("银行返回" + errMsg + "," + bankOrder.getOrderId());
			noCodeSet.put(bankOrder.getOrderId(), bankOrder.getOrderId());
			if (noCodeSet.size() == maxTime) {
				fuseWiring("累计10笔银行返回错误码为空中断付款作业," + errMsg, bankOrder.getOrderId());
				return;
			}
			
		}
	}
	
	

	public String getBank() {
		return bank;
	}

	public void setBank(String bank) {
		this.bank = bank;
	}

	public String getStlmEx() {
		return stlmEx;
	}

	public void setStlmEx(String stlmEx) {
		this.stlmEx = stlmEx;
	}
	
	public void setMaxTime(Integer maxTime) {
		this.maxTime = maxTime;
	}

	@Override
	public void batchPatrol(String packetNo, BcdlResult result, BcdlPacketRes packetRes) {
		// TODO Auto-generated method stub
		logger.error("[sentinel_batch_patrol_start]" + bank + ", PacketNo = " + packetNo);
		try {
			if (null == result) {
				fuseWiring( "未知结果中断付款作业", "PacketNo = "+packetNo);
				return;
			}
			
			if (ProcCodeConstants.PROC_CODE_100E6082.equals(result.getResultCode())) {
				// 银企直连类交易转账签名异常,中断交易发起
				fuseWiring( "签名异常中断付款作业", "PacketNo = "+packetNo);
				return;
			}
			
			if (ProcCodeConstants.PROC_CODE_100E6047.equals(result.getResultCode())) {
				batchTimeoutCount.incrementAndGet();
				if (batchTimeoutCount.get()==2) {
					fuseWiring( "连续2笔超时中断付款作业", "PacketNo = "+packetNo);
					return;
				}
			} else {
				batchTimeoutCount.set(0);;
			}
			
			if(!ProcCodeConstants.PROC_CODE_100E6047.equals(result.getResultCode())
					&&!ProcCodeConstants.PROC_CODE_100S1000.equals(result.getResultCode())){
				fuseWiring( "批量订单付款未成功中断付款作业", "PacketNo = "+packetNo);
				return;
			}
			
		} catch (Exception e) {
			logger.error("[sentinel_patrol_error]" + bank + ",哨兵异常"+ "," + "PacketNo = "+packetNo, e);
			fuseWiring( "哨兵异常中断付款作业", "PacketNo = "+packetNo);
			return;
		}
		logger.error("[sentinel_patrol_end]" + bank + "," + "PacketNo = "+packetNo);
	}

	@Override
	public void stopForTime(Long sq) {
		fuseWire = true;
		bankSentinelInfoDAO.updateSentinelAndTimeFuse(1, sentinelId, sq);
	}

	@Override
	public void restartForTime(Long sq) {
		timeoutCount.set(0);
		sysErrorCount.set(0);
		fusedSet.clear();
		noCodeSet.clear();
		batchTimeoutCount.set(0);
		fuseWire = false;
		bankSentinelInfoDAO.updateSentinelAndTimeFuse(0, sentinelId,sq);
	}

	public String getSentinelId() {
		return sentinelId;
	}

	public void setSentinelId(String sentinelId) {
		this.sentinelId = sentinelId;
	}

	@Override
	public void batchCheckPatrol(String packetNo, BcdlPacketRes packetRes) {
		if (isFuse()) {
			logger.error("[sentinel_patrol_error]--付款任务已熔断，不再处理"+packetNo);
			return;
		}
	    logger.error("[sentinel_patrol_start]" + bank + "," + "PacketNo = "+packetNo);
		try {
			if(packetRes.isSuccess()&&packetRes.getBcdlResults()!=null){
				for(BcdlResult result:packetRes.getBcdlResults()){
					BankOrder bankOrder = new BankOrder();
					if (null == result) {
						fuseWiring("未知结果中断付款作业", "PacketNo = "+packetNo);
						return;
					}
					bankOrder.setOrderId(result.getGopayOrderId());
					//调用熔断处理逻辑
					if (ProcCodeConstants.PROC_CODE_100E6027.equals(result.getResultCode()) || ProcCodeConstants.PROC_CODE_100E6066.equals(result.getResultCode()) ) {
						fused(bankOrder,result.getBankRetCode(),result.getErrorMsg());
					}
					
//					if (ProcCodeConstants.PROC_CODE_100F1002.equals(result.getErrCode())) {
//						sysErrorCount.incrementAndGet();
//						if (sysErrorCount.get() == 5) {
//							fuseWiring("连续5笔内部系统异常中断付款作业", bankOrder.getOrderId());
//							return;
//						}
//					} else {
//						sysErrorCount.set(0);;
//					}
				}
			}
					

		} catch (Exception e) {
			logger.error("[sentinel_patrol_error]" + bank + ",批量对账哨兵异常" + "," + "PacketNo = "+packetNo, e);
			fuseWiring("批量对账哨兵异常中断付款作业", packetNo);
			return;
		}
		logger.error("[sentinel_patrol_end]" + bank + "," + "PacketNo = "+packetNo);
		
	}
	
}
